package com.zhuliwei.utils;// 文件：WeightRecognizer.java

import com.zhuliwei.entity.*;
import com.zhuliwei.enums.ExceptionEnum;
import com.zhuliwei.exception.RecognitionException;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 重量识别工具类
 *
 * 本类实现了根据开门和关门时各层的重量数据，
 * 对消费者的购物清单进行识别的算法。
 *
 * 说明：
 * 1. 对每层架，首先校验传感器数据是否在合法范围 [0,32767] 内。
 * 2. 若某层开门和关门的重量数据异常，记录传感器异常。
 * 3. 若某层重量未减少（反而增加），则记录异物异常。
 * 4. 若通过传感器重量差无法唯一匹配出合适的商品组合，
 *    则记录无法识别异常；同时保留其他层的数据，供其他方式识别参考。
 * 5. 同一商品在多个层识别后，需合并为一个 RecognitionItem 项目。
 *
 * @author 朱力维
 * @time 2025/3/21 13:56
 **/
public class WeightRecognizerUtil {
    // 常量定义：层数和重量传感器正常范围
    private static final int TOTAL_LAYERS = 10;
    private static final int MIN_WEIGHT = 0;
    private static final int MAX_WEIGHT = 32767;

    /**
     * 识别购物清单的主方法
     *
     * @param openLayers   开门时各层的重量数据（长度应为 TOTAL_LAYERS）
     * @param closeLayers  关门时各层的重量数据（长度应为 TOTAL_LAYERS）
     * @param goodsList    所有在售商品列表
     * @param stockList    各层对应的库存情况
     * @param sensorTolerance 传感器容差 ST，单位 g
     * @param packageTolerance 包装容差，百分比（如 5 表示 5%）
     * @return RecognitionResult 识别结果对象
     */
    public RecognitionResult recognize(List<Layer> openLayers,
                                       List<Layer> closeLayers,
                                       List<Goods> goodsList,
                                       List<Stock> stockList,
                                       int sensorTolerance,
                                       double packageTolerance) {
        // 创建结果对象，默认识别成功
        RecognitionResult result = new RecognitionResult();

        // 用于临时保存各层识别出的 RecognitionItem
        Map<String, RecognitionItem> itemMap = new HashMap<>();

        // 遍历每一层，索引从1到 TOTAL_LAYERS
        for (int i = 1; i <= TOTAL_LAYERS; i++) {
            // 过滤出对应层的数据
            int finalI = i;
            Optional<Layer> openLayerOpt = openLayers.stream().filter(l -> l.getIndex() == finalI).findFirst();
            int finalI1 = i;
            Optional<Layer> closeLayerOpt = closeLayers.stream().filter(l -> l.getIndex() == finalI1).findFirst();

            // 若数据不完整，则跳过该层（也可记录异常）
            if (!openLayerOpt.isPresent() || !closeLayerOpt.isPresent()) {
                // 数据缺失视为传感器异常
                result.getExceptions().add(new RecognitionException(i, ExceptionEnum.SENSOR_EXCEPTION, 0, 0));
                result.setSuccessful(false);
                continue;
            }

            Layer openLayer = openLayerOpt.get();
            Layer closeLayer = closeLayerOpt.get();

            int openWeight = openLayer.getWeight();
            int closeWeight = closeLayer.getWeight();

            // 步骤1：校验传感器数据是否在正常范围内
            if (openWeight < MIN_WEIGHT || openWeight > MAX_WEIGHT || closeWeight < MIN_WEIGHT || closeWeight > MAX_WEIGHT) {
                result.getExceptions().add(new RecognitionException(i, ExceptionEnum.SENSOR_EXCEPTION, openWeight, closeWeight));
                result.setSuccessful(false);
                // 即使异常，也继续处理其他层
                continue;
            }

            // 步骤2：判断重量是否有减少
            int diff = openWeight - closeWeight;
            if (diff < 0) {
                // 如果重量反而增加，则认为消费者放置了外来物品
                result.getExceptions().add(new RecognitionException(i, ExceptionEnum.FOREIGN_OBJECT_EXCEPTION, openWeight, closeWeight));
                result.setSuccessful(false);
                continue;
            }

            // 步骤3：如果重量变化在传感器容差范围内，视为无购物行为
            if (Math.abs(diff) <= sensorTolerance) {
                // 无购物，直接跳过识别（不记录异常）
                continue;
            }

            // 步骤4：识别该层购物的商品组合
            // 根据 stockList 查找该层所有销售的商品
            int finalI2 = i;
            List<Stock> stocksInLayer = stockList.stream()
                    .filter(s -> s.getLayer() == finalI2)
                    .collect(Collectors.toList());

            // 用于记录符合条件的候选商品和对应的取件数量
            Map<String, Integer> candidateMap = new HashMap<>();

            // 对于每个销售的商品，尝试计算购物件数
            for (Stock stock : stocksInLayer) {
                // 根据 goodsId 从 goodsList 中查找商品信息
                Optional<Goods> goodsOpt = goodsList.stream()
                        .filter(g -> g.getId().equals(stock.getGoodsId()))
                        .findFirst();
                if (!goodsOpt.isPresent()) {
                    // 商品信息缺失，跳过该商品
                    continue;
                }
                Goods goods = goodsOpt.get();
                int singleWeight = goods.getWeight();

                // 考虑包装容差：实际单件重量允许浮动范围
                // 例如，包装容差为 5%，则实际重量可能在 [0.95*singleWeight, 1.05*singleWeight]
                double lowerBound = singleWeight * (1 - packageTolerance / 100.0);
                double upperBound = singleWeight * (1 + packageTolerance / 100.0);

                // 计算可能的数量：由于大部分消费者只拿 1~2 个，所以我们尝试1到最多库存数
                int validCount = 0;
                int candidateCount = 0;
                for (int count = 1; count <= stock.getNum(); count++) {
                    // 计算理论重量（不考虑传感器容差）：
                    // 因为包装存在浮动，理论重量范围为 [count*lowerBound, count*upperBound]
                    double totalLower = count * lowerBound;
                    double totalUpper = count * upperBound;

                    // 考虑传感器容差，允许误差 sensorTolerance
                    // 判断 diff 是否在 [totalLower - sensorTolerance, totalUpper + sensorTolerance] 范围内
                    if (diff >= totalLower - sensorTolerance && diff <= totalUpper + sensorTolerance) {
                        candidateCount = count;
                        validCount++;
                    }
                }

                // 如果仅有唯一的数量匹配，则记录该商品作为候选
                if (validCount == 1) {
                    candidateMap.put(goods.getId(), candidateCount);
                }
            }

            // 步骤5：判断该层是否能唯一识别出购物商品
            if (candidateMap.size() == 1) {
                // 只有一个商品被匹配到，记录识别结果
                String goodsId = candidateMap.keySet().iterator().next();
                int count = candidateMap.get(goodsId);

                // 如果同一商品在其它层也识别出，则进行数量累加
                if (itemMap.containsKey(goodsId)) {
                    itemMap.get(goodsId).addNum(count);
                } else {
                    itemMap.put(goodsId, new RecognitionItem(goodsId, count));
                }
            } else {
                // 无法唯一确定购物商品或存在多个候选，记录无法识别异常
                result.getExceptions().add(new RecognitionException(i, ExceptionEnum.UNRECOGNIZED_EXCEPTION, openWeight, closeWeight));
                result.setSuccessful(false);
            }
        }

        // 步骤6：设置最终识别结果
        // 将候选 RecognitionItem 合并到结果中（同一商品只出现一次）
        result.setItems(new ArrayList<>(itemMap.values()));

        return result;
    }
}
